/***************************************************************************
 *   Copyright (C) 2009 by Geoffrey McRae   *
 *   geoff@spacevs.com   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#ifdef VIDEO_XV

#include "xv.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xv.h>
#include <X11/extensions/Xvlib.h>
#include <X11/extensions/XShm.h>

extern int 	XShmQueryExtension(Display*);
extern int 	XShmGetEventBase(Display*);
extern XvImage  *XvShmCreateImage(Display*, XvPortID, int, char*, int, int, XShmSegmentInfo*);

#define GUID_YV12	0x32315659
#define GUID_I420	0x30323449

typedef struct {
	Display			*dpy;
	int			screen;
	XVisualInfo		vinfo;
	Window			window;
	XSetWindowAttributes	xswa;
	int			xv_port;
	GC			gc;
	unsigned long		format;
	unsigned int		width, height;
	unsigned int		actual_width, actual_height;
} mpr_video_xv_data_t;

typedef struct {
	XShmSegmentInfo		shminfo;
	XvImage			*yuv_image;
} mpr_video_xv_frame_t;

void *mpr_video_xv_new(void *data) {
	mpr_video_xv_data_t *xv;
	xv = calloc(sizeof(mpr_video_xv_data_t), 1);

	int		i;
	XvAdaptorInfo	*ai;
	unsigned int	p_num_adaptors;

	if (!(xv->dpy = XOpenDisplay(NULL))) {
		ERR("%s", "Unable to open Display.");
		free(xv);
		return NULL;
	}

	if (!XShmQueryExtension(xv->dpy)) {
		ERR("%s", "No shared memory support");
		free(xv);
		return NULL;
	}

	if(XvQueryAdaptors(xv->dpy, DefaultRootWindow(xv->dpy), &p_num_adaptors, &ai) != Success) {
		ERR("%s", "XvQueryAdaptors Failed");
		free(xv);
		return NULL;
	}

	xv->xv_port = -1;
	for(i = 0; i < p_num_adaptors; ++i) {
		xv->xv_port = ai[i].base_id + 1;
		break;
	}
	XvFreeAdaptorInfo(ai);
	xv->screen = DefaultScreen(xv->dpy);

		if (XMatchVisualInfo(xv->dpy, xv->screen, 24, TrueColor  , &xv->vinfo)) INF("%s", "Found 24bit TrueColor" );
	else    if (XMatchVisualInfo(xv->dpy, xv->screen, 16, TrueColor  , &xv->vinfo)) INF("%s", "Found 16bit TrueColor" );
	else    if (XMatchVisualInfo(xv->dpy, xv->screen, 15, TrueColor  , &xv->vinfo)) INF("%s", "Found 15bit TrueColor" );
	else    if (XMatchVisualInfo(xv->dpy, xv->screen,  8, PseudoColor, &xv->vinfo)) INF("%s", "Found 8bit PsuedoColor");
	else    if (XMatchVisualInfo(xv->dpy, xv->screen,  8, GrayScale  , &xv->vinfo)) INF("%s", "Found 8bit GrayScale"  );
	else    if (XMatchVisualInfo(xv->dpy, xv->screen,  8, StaticGray , &xv->vinfo)) INF("%s", "Found 8bit StaticGray" );
	else    if (XMatchVisualInfo(xv->dpy, xv->screen,  1, StaticGray , &xv->vinfo)) INF("%s", "Found 1bit StaticGray" );
	else {
		ERR("%s", "Requires 16 bit display");
		free(xv);
		return NULL;
	}

	xv->xswa.colormap		= XCreateColormap(xv->dpy, DefaultRootWindow(xv->dpy), xv->vinfo.visual, AllocNone);
	xv->xswa.event_mask		= StructureNotifyMask | ExposureMask;
	xv->xswa.background_pixel	= 0;
	xv->xswa.border_pixel		= 0;

	return xv;
}

void mpr_video_xv_free(void *data) {
	mpr_video_xv_data_t *xv = data;
	XFreeColormap(xv->dpy, xv->xswa.colormap);
	XCloseDisplay(xv->dpy);
	free(xv);
}

bool mpr_video_xv_open(void *data, unsigned int width, unsigned int height, unsigned int actual_width, unsigned int actual_height, unsigned int pix_fmt) {
	mpr_video_xv_data_t *xv = data;

	XEvent event;

	xv->window = XCreateWindow(
		xv->dpy, DefaultRootWindow(xv->dpy),
		0, 0,
		width, height,
		0, xv->vinfo.depth,
		InputOutput,
		xv->vinfo.visual,
		CWBackPixel | CWBorderPixel | CWColormap | CWEventMask,
		&xv->xswa
	);

	XSelectInput	(xv->dpy, xv->window, StructureNotifyMask);
	XMapWindow	(xv->dpy, xv->window);
	do XNextEvent(xv->dpy, &event);
	while (event.type != MapNotify || event.xmap.event != xv->window);

	xv->gc = XCreateGC(xv->dpy, xv->window, 0, 0);

	switch(pix_fmt) {
		case PIX_FMT_YUV420P: xv->format = GUID_YV12; break;
		default:
			ERR("%s", "I cant handle this format yet");
			return false;
	}

	xv->width		= width;
	xv->height		= height;
	xv->actual_width	= actual_width;
	xv->actual_height	= actual_height;

	return true;
}

void mpr_video_xv_close(void *data) {
	mpr_video_xv_data_t *xv = data;
	
	XEvent event;

	XDestroyWindow(xv->dpy, xv->window);
	do XNextEvent(xv->dpy, &event);
	while (event.type != DestroyNotify || event.xmap.event != xv->window);
}

bool mpr_video_xv_queue_new(void *data, AVFrame *pFrame, void **storage) {
	mpr_video_xv_data_t	*xv	= data;
	mpr_video_xv_frame_t	*f;
	f = calloc(sizeof(mpr_video_xv_frame_t), 1);
	*storage = f;

	f->yuv_image		= XvShmCreateImage(xv->dpy, xv->xv_port, xv->format, 0, xv->actual_width, xv->actual_height, &f->shminfo);
	f->shminfo.shmid	= shmget(IPC_PRIVATE, f->yuv_image->data_size, IPC_CREAT | 0777);
	f->shminfo.shmaddr	= f->yuv_image->data = shmat(f->shminfo.shmid, 0, 0);
	f->shminfo.readOnly	= False;

	if (!XShmAttach(xv->dpy, &f->shminfo)) {
		ERR("%s", "XShmAttach Failed");
		return false;
	}

	return true;
}

void mpr_video_xv_queue_free(void *data, AVFrame *pFrame, void **storage) {
	mpr_video_xv_data_t	*xv	= data;
	mpr_video_xv_frame_t	*f	= *storage;

	XShmDetach(xv->dpy, &f->shminfo);
	shmdt(f->shminfo.shmaddr);
	XFree(f->yuv_image);
	free(f);
}

bool mpr_video_xv_queue(void *data, AVFrame *pFrame, void **storage) {
	mpr_video_xv_frame_t	*f	= *storage;

	XvImage			*img = f->yuv_image;
	int			y, h;

	h = img->height / 2;

	/* Y Plane */
	for(y = 0; y < img->height; ++y) {
		memcpy(
			&img->data[img->offsets[0] + (y * img->pitches[0])],
			&pFrame->data[0][y * pFrame->linesize[0]],
			img->pitches[0]
		);
	}

	/* U Plane */
	for(y = 0; y < h; ++y) {
		memcpy(
			&img->data[img->offsets[2] + (y * img->pitches[2])],
			&pFrame->data[1][y * pFrame->linesize[1]],
			img->pitches[2]
		);
	}

	/* V Plane */
	for(y = 0; y < h; ++y) {
		memcpy(
			&img->data[img->offsets[1] + (y * img->pitches[1])],
			&pFrame->data[2][y * pFrame->linesize[2]],
			img->pitches[1]
		);
	}

	return true;
}

bool mpr_video_xv_display(void *data, void **storage) {
	mpr_video_xv_data_t	*xv	= data;
	mpr_video_xv_frame_t	*f	= *storage;

	int _d;
	unsigned int _w, _h, _dd;
	Window _dw;
	XvImage *img;

	img = f->yuv_image;
	XGetGeometry(xv->dpy, xv->window, &_dw, &_d, &_d, &_w, &_h, &_dd, &_dd);
	XvShmPutImage(xv->dpy, xv->xv_port, xv->window, xv->gc,
		img,
		0, 0, img->width, img->height,
		0, 0, _w, _h,
		True
	);
	
	return true;
}

void mpr_video_xv_drop(void *data, void **storage) {
	/*
	mpr_video_xv_data_t	*xv	= data;
	mpr_video_xv_frame_t	*f	= *storage;
	*/
}

#endif